package org.blindmandesign;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.    
 */

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * <pre>
 * Example uses:
 *   rd.parse('table#id tr.bar.foo td[baz=woogie]');
 *
 * Output:
 *   <table id="id">
 *     <tr class="bar foo">
 *       <td baz="woogie"/>
 *     </tr>
 *   </table>
 *
 * @author Nathan Bubna
 * @since javaQuery 1.0
 * @version $Id$
 */
public class SelectorParser implements Parser {

    private Config config;
    private static enum Mode { NAME, ID, CLASS, ATTR }

    public SelectorParser() {
        this(new Config());
    }

    public SelectorParser(Config config) {
        if (config == null) {
            throw new NullPointerException("Config is required");
        }
        this.config = config;
    }

    public Config getConfig() {
        return config;
    }

    public List<Node> parse(String definition) {
        String[] tags = split(definition);
        Tag last = null;
        for (int i=0; i < tags.length; i++) {
            Tag tag = parseSingle(tags[i]);
            if (last != null) {
                last.append(tag);
            }
            last = tag;
        }
        List<Node> nodes = new ArrayList<Node>(1);
        nodes.add(last);
        return nodes;
    }

    protected String[] split(String me) {
        //TODO: fix escaped delimiters
        return me.split(config.getDelimiter());
    }

    protected Tag parseSingle(String definition) {
        StringBuilder store = new StringBuilder();
        Tag tag = new Tag();
        Mode mode = Mode.NAME;
        for (int i=0; i < definition.length(); i++) {
            char c = definition.charAt(i);
            if (c == '#') {
                store = clear(mode, tag, store, true);
                mode = Mode.ID;
            }
            else if (c == '.') {
                store = clear(mode, tag, store, true);
                mode = Mode.CLASS;
            }
            else if (c == '[') {
                store = clear(mode, tag, store, true);
                mode = Mode.ATTR;
            }
            else if (c == ']') {
                store = clear(mode, tag, store, true);
                mode = Mode.NAME;
            }
            else {
                store.append(c);
            }
        }
        clear(mode, tag, store, false);
        return tag;
    }

    private StringBuilder clear(Mode mode, Tag tag,
                                StringBuilder val, boolean emptyStore) {
        if (val.length() > 0) {
            String s = val.toString();
            switch (mode) {
                case NAME:
                    tag.name(s);
                    break;
                case ID:
                    tag.id(s);
                    break;
                case CLASS:
                    tag.addClass(s);
                    break;
                case ATTR:
                    if (s.indexOf('=') > 0) {
                        String[] kv = s.split("=");
                        tag.attr(kv[0], kv[1]);
                    }
                    else {
                        tag.attr(s, null);
                    }
                    break;
            }
            if (emptyStore) {
                return new StringBuilder();
            }
            return val;
        }
        else {
            // already is clean
            return val;
        }
    }

}
